package pl.edu.pw.elka.tinyrepo.common;

import java.io.BufferedWriter;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.StringTokenizer;

public abstract class Message {
   
   /**
    * Wlasna, recznie zrobiona metoda do wczytywnia calej linii.
    * Jest zamiennikiem BufferedReader.readLine(). UWAGA: BufferedReader nie nadaje
    * sie do czytania socketa, ktory ma byc potem gdzies przekazany! -> bo BufferedReader
    * odczytuje z nadmiarem do swojego bufora.
    * FIXME na razie nie uznaje za nowa linie CR+LF - i tak niech pozostanie.
    * TODO miejsce potencjalnych bledow, jakby cos sie dzialo, trzeba patrzec tutaj. 
    * 
    * @param in
    * @return
    * @throws IOException
    */
   public static String readInputLine(InputStream in) throws IOException {
      byte character;
      StringBuffer buffer = new StringBuffer("");
      while((character = (byte)in.read()) != -1) {
         if((byte)character == '\n' || (byte)character == '\r') // jeszcze ewentualnie sprawdzac CR+LF
            break;
         buffer.append((char)character);
      }
      return buffer.toString();
   }
   
   
   /** 
    * Wczytuje wszystkie naglowki z requesta/responsa. Nie wczytuje danych bezposrednio za Requestem.
    * Nie wczytuje tez request/response line.  
    * <b> Po wykonaniu tej metdy strumien in wskazuje na pierwszy bajt danych (pierwszy bajt Message Body). </b>
    * 
    * @param in - strumien, ktory po poprawnym zakonczeniu wskazuje na poczatek danych.
    */
   public void readHeaders(InputStream in) { 
      String line;
      try {
         while((line = Message.readInputLine(in)) != null && line.length() != 0) { // czytanie az dojdzie do CRLF
           StringTokenizer t = new StringTokenizer(line);
           String header = t.nextToken(); // wyciagniecie pierwszego tokena z linii
           HTTPHeaderType type = HTTPHeaderType.getType(header);
           if(type == null) { 
              System.err.println("Request: Unknown header for: "+line+", skipping...");
              continue;
           }
           // wydobywanie wartosci dla naglowka.
           String value = t.nextToken(""); //pobranie pozostalej czesci linii
           // value moze jednak na poczatku miec spacje (ktore nie pojawily sie w header) 
           if(value == null || value.length() == 0) { // brak wartosci lub wartosc pusta
              System.err.println("Request: No value for: "+line+", skipping...");
              continue;
           }
           StringBuffer modifier = new StringBuffer(value);
           while(modifier.length() != 0 && Character.isWhitespace(modifier.charAt(0))) // usuwanie bialych znakow z poczatku
              modifier.deleteCharAt(0);
           if(modifier.length() == 0) {
              System.err.println("Request: Wrong value: "+line+", skipping...");
              continue;
           }
           // modifier juz ma prawidlowa wartosc
           addHeader(type, modifier.toString()); // TODO nie jest sprawadzane czy typ naglowka sie nie powtarza w strumieniu - na razie jest tak zrobione, ze brany 
                                                   // ze brany jest pod uwage ostatnio dodany naglowek.
           
        }
      } catch (IOException e) {
         // jak wywali blad przy czytaniu danych, to po prostu wypisz w serwerze ze pojawil sie blad 
         throw new RuntimeException("Request: Connection problem while reading header");
      }
      
   }
   
   /**
    * Zapisuje request/response line i naglowki do strumienia. </br>
    * 
    * <b>FIXME UWAGA nie jest sprawdzane czy sa poprawne wartosci w poszczegolnych polach obiektu Request/Response
    * dlatego nalezy stosowowac z rozwaga ta funkcje!</b>
    * </br>
    * UWAGA2: naglowki sa wypisywanie wg kolejnosci deklaracji enumow w HTTPHeaderType (patrz comparteTo dla enumow).
    * </br>UWAGA3: funkcja nie zamyka strumienia (powinno to sie zrobic na zewnatrz).
    * @param out 
    */
   public void write(OutputStream out) {
      BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(out));
      try {
         writer.write(getFirstLine()); // zapisanie pierwszej linii
         // kolejnosc wypisywania naglowkow jest istotna
         List<HTTPHeaderType> sortedHeaders = 
                           new LinkedList<HTTPHeaderType>(getHTTPTypes());
         Collections.sort(sortedHeaders);
         for(int i = 0; i < sortedHeaders.size(); i++) { // wypisywanie n-1 kolejnych naglowkow
            HTTPHeaderType t = sortedHeaders.get(i);
            writer.write("\n"+t+" "+getHeader(t)); // FIXME potencjalna mozliwosc poprawy wydajnosci
         } 
         writer.write("\r\n");// CRLF 
         writer.flush(); 
      } catch (IOException e) {
         // nic nie wysylac, bo sie wiecej nie da - sa jakies problemy z polaczeniem z drugim hostem.
         throw new RuntimeException("Request: Connection problem while writing header");
      }
   }
   
   
   @Override
   public String toString() {
      ByteArrayOutputStream baos = new ByteArrayOutputStream();
      write(baos);
      return new String(baos.toByteArray());
   }
   
   /**
    * @return odpowiednio request/response line.
    */
   public abstract String getFirstLine();
   
   /**
    * @param t typ.
    * @return wartosc naglowka, np. dla CONTENT_LENGTH bedzie to rozmiar przesylanych danych (ilosc bajtow pliku).
    */
   public abstract String getHeader(HTTPHeaderType t);
   
   /**
    * Dodaje naglowek do podanego requesta.
    * Wypisuje na System.err informacje, jezeli wartosc naglowka nie jest poprawna (np. dla CONTENT_LENGTH powinien to byc String z samymi cyframi).
    * 
    * @param t - rodzaj naglowka.
    * @param value - wartosc dla tego naglowka
    */
   public abstract void addHeader(HTTPHeaderType t, String value);
   
   /**
    * @return zbior naglowkow dostepnych w tym Request/Response.
    */
   abstract Set<HTTPHeaderType> getHTTPTypes(); 
   
   
   /**
    * Pozwala doczytac dane z strumienia wejsciowego do tablicy 
    * TODO zastosowac, gdy bedzie konieczne, na razie nie nadaje 
    * sie do wykorzystania ze wzgledu na duze zapotrzebowanie pamieci.
    * 
    * @param in
    */
   @Deprecated
   static byte[] readData(InputStream in, Integer bufferSize) {
      byte[] buffer = new byte[bufferSize]; 
      List<Byte> data = new ArrayList<Byte>();

      int amount = 0; // ile sie wczytalo do bufora
      return null;
//      while((amount = in.read(buffer)) != -1) { // pobieraj porcje o rozmiarze bufora, az dojdziesz do konca
//         .write(buffer, 0, amount); // wyslij bufor do klienta.
//      }
//      out.flush(); 
   }
}